#include "Hdr2SdrNative.h"
#include "av_codec_sample_error.h"
#include <cstdint>

#undef LOG_TAG
#define LOG_TAG "HDR2SDR"

struct AsyncCallbackInfo {
    napi_env env;
    napi_async_work asyncWork;
    napi_deferred deferred;

    int32_t inputFd = 0;
    int64_t inputOffset = 0;
    int64_t inputSize = 0;
    int32_t inputFd1 = 0;
    int64_t inputOffset1 = 0;
    int64_t inputSize1 = 0;

    int32_t resultCode = 0;
    std::string resultStr = "";
    int64_t durationTime = 0;
    int32_t videoWidth = 0;
    int32_t videoHeight = 0;
};

void DealCallBack(napi_env env, void *data) {
    AsyncCallbackInfo *asyncCallbackInfo = static_cast<AsyncCallbackInfo *>(data);

    napi_value durationTime;
    napi_create_int64(env, asyncCallbackInfo->durationTime, &durationTime);
    napi_value code;
    napi_create_int32(env, asyncCallbackInfo->resultCode, &code);
    napi_value videoWidth;
    napi_create_int32(env, asyncCallbackInfo->videoWidth, &videoWidth);
    napi_value videoHeight;
    napi_create_int32(env, asyncCallbackInfo->videoHeight, &videoHeight);
    napi_value message;
    napi_create_string_utf8(env, asyncCallbackInfo->resultStr.data(), NAPI_AUTO_LENGTH, &message);
    napi_value obj;
    napi_create_object(env, &obj);

    napi_set_named_property(env, obj, "code", code);
    napi_set_named_property(env, obj, "message", message);
    napi_set_named_property(env, obj, "durationTime", durationTime);
    napi_set_named_property(env, obj, "videoWidth", videoWidth);
    napi_set_named_property(env, obj, "videoHeight", videoHeight);
    napi_resolve_deferred(asyncCallbackInfo->env, asyncCallbackInfo->deferred, obj);
    napi_delete_async_work(env, asyncCallbackInfo->asyncWork);
    delete asyncCallbackInfo;
}

void SetCallBackResult(AsyncCallbackInfo *asyncCallbackInfo, int32_t code, std::string str) {
    asyncCallbackInfo->resultCode = code;
    asyncCallbackInfo->resultStr = str;
}

void SetInitCallBack(AsyncCallbackInfo *asyncCallbackInfo, int64_t durationTime, int32_t videoWidth, int32_t videoHeight) {
    asyncCallbackInfo->durationTime = durationTime;
    asyncCallbackInfo->videoWidth = videoWidth;
    asyncCallbackInfo->videoHeight = videoHeight;
}

void NativePlay(napi_env env, void *data) {
    AsyncCallbackInfo *asyncCallbackInfo = static_cast<AsyncCallbackInfo *>(data);
    for(int32_t i = 0; i < 2; ++i){
        InstanceManager::GetInstance().SampleMap_.at(NativeXComponentSample::PluginManager::GetInstance()->idList[i])->Start();
    }
    int32_t ret = InstanceManager::GetInstance().SampleMap_.at(NativeXComponentSample::PluginManager::GetInstance()->idList[1])->WaitForDone();
//     ret = InstanceManager::GetInstance().SampleMap_.at(NativeXComponentSample::PluginManager::GetInstance()->idList[1])->WaitForDone();
    if (ret != AVCODEC_SAMPLE_ERR_OK) {
        SetCallBackResult(asyncCallbackInfo, -1, "Native Start failed");
        ret = InstanceManager::GetInstance()
                  .SampleMap_.at(NativeXComponentSample::PluginManager::GetInstance()->idList[0])
                  ->Stop();
        ret = InstanceManager::GetInstance()
                  .SampleMap_.at(NativeXComponentSample::PluginManager::GetInstance()->idList[1])
                  ->Stop();
        if (ret != AVCODEC_SAMPLE_ERR_OK) {
            SetCallBackResult(asyncCallbackInfo, -1, "Native stop failed");
        }
    }
    SetCallBackResult(asyncCallbackInfo, 0, "Native play done.");
}

napi_value Hdr2SdrNative::Play(napi_env env, napi_callback_info info) {
    napi_deferred deferred;
    napi_value promise;
    napi_create_promise(env, &deferred, &promise);

    AsyncCallbackInfo *asyncCallbackInfo = new AsyncCallbackInfo();
    asyncCallbackInfo->env = env;
    asyncCallbackInfo->asyncWork = nullptr;
    asyncCallbackInfo->deferred = deferred;

    napi_value resourceName;
    napi_create_string_latin1(env, "NativePlay", NAPI_AUTO_LENGTH, &resourceName);
    napi_create_async_work(
        env, nullptr, resourceName, [](napi_env env, void *data) { NativePlay(env, data); },
        [](napi_env env, napi_status status, void *data) { DealCallBack(env, data); }, (void *)asyncCallbackInfo,
        &asyncCallbackInfo->asyncWork);
    napi_queue_async_work(env, asyncCallbackInfo->asyncWork);
    return promise;
}

napi_value Hdr2SdrNative::Pause(napi_env env, napi_callback_info info) {
    int32_t ret = InstanceManager::GetInstance().SampleMap_.at(NativeXComponentSample::PluginManager::GetInstance()->idList[0])->Pause();
    ret = InstanceManager::GetInstance().SampleMap_.at(NativeXComponentSample::PluginManager::GetInstance()->idList[1])->Pause();
    if(ret != AVCODEC_SAMPLE_ERR_OK) {
        AVCODEC_SAMPLE_LOGI("Native pause failed");
    }
    return nullptr;
}

napi_value Hdr2SdrNative::Resume(napi_env env, napi_callback_info info) {
    int32_t ret = InstanceManager::GetInstance()
                      .SampleMap_.at(NativeXComponentSample::PluginManager::GetInstance()->idList[0])
                      ->Resume();
    ret = InstanceManager::GetInstance()
                      .SampleMap_.at(NativeXComponentSample::PluginManager::GetInstance()->idList[1])
                      ->Resume();
    if (ret != AVCODEC_SAMPLE_ERR_OK) {
        OH_LOG_ERROR(LOG_APP, "Native resume error");
    }
    return nullptr;
}

napi_value Hdr2SdrNative::Seek(napi_env env, napi_callback_info info) {
    int64_t currentTime = 0;
    size_t argc = 1;
    napi_value args[1] = {0};
    napi_get_cb_info(env, info, &argc, args, nullptr, nullptr);
    napi_get_value_int64(env, args[0], &currentTime);
    
    int32_t ret = InstanceManager::GetInstance().SampleMap_.at(NativeXComponentSample::PluginManager::GetInstance()->idList[0])->VideoSeek(currentTime);
    ret = InstanceManager::GetInstance().SampleMap_.at(NativeXComponentSample::PluginManager::GetInstance()->idList[1])->VideoSeek(currentTime);
    if (ret != AVCODEC_SAMPLE_ERR_OK) {
       AVCODEC_SAMPLE_LOGI("Native seek failed");
    }
    return nullptr;
}

napi_value Hdr2SdrNative::Stop(napi_env env, napi_callback_info info) {
    int ret;
    if (InstanceManager::GetInstance().SampleMap_.find(
        NativeXComponentSample::PluginManager::GetInstance()->idList[0]) !=
    InstanceManager::GetInstance().SampleMap_.end()) {
            ret = InstanceManager::GetInstance()
                      .SampleMap_.at(NativeXComponentSample::PluginManager::GetInstance()->idList[0])
                      ->Stop();
    }
    if (InstanceManager::GetInstance().SampleMap_.find(
            NativeXComponentSample::PluginManager::GetInstance()->idList[0]) !=
        InstanceManager::GetInstance().SampleMap_.end()) {
    ret = InstanceManager::GetInstance()
                      .SampleMap_.at(NativeXComponentSample::PluginManager::GetInstance()->idList[1])
                      ->Stop();
    }
    if (ret != AVCODEC_SAMPLE_ERR_OK) {
        OH_LOG_ERROR(LOG_APP, "Native stop error");
    }
    return nullptr;
}

napi_value Hdr2SdrNative::Loop(napi_env env, napi_callback_info info) {
    bool isLoop = false;
    size_t argc = 1;
    napi_value args[1] = {nullptr};
    napi_get_cb_info(env, info, &argc, args, nullptr, nullptr);
    napi_get_value_bool(env, args[0], &isLoop);
    for(int32_t i = 0;i<2;i++){
        if (InstanceManager::GetInstance().SampleMap_.find(
            NativeXComponentSample::PluginManager::GetInstance()->idList[i]) !=
        InstanceManager::GetInstance().SampleMap_.end()) {
            int32_t ret =
                InstanceManager::GetInstance()
                    .SampleMap_.at(NativeXComponentSample::PluginManager::GetInstance()->idList[i])
                    ->Loop(isLoop);
            if (ret != AVCODEC_SAMPLE_ERR_OK) {
                OH_LOG_ERROR(LOG_APP, "Native Loop error");
            }
        }
    }
    return nullptr;
}

napi_value Hdr2SdrNative::SetSurface(napi_env env, napi_callback_info info) {
    bool isAutoSwitch = false;
    size_t argc = 1;
    napi_value args[1] = {nullptr};
    napi_get_cb_info(env, info, &argc, args, nullptr, nullptr);
    napi_get_value_bool(env, args[0], &isAutoSwitch);
    
    int32_t ret = InstanceManager::GetInstance()
                      .SampleMap_.at(NativeXComponentSample::PluginManager::GetInstance()->idList[0])
                      ->SetSurfaceNative(isAutoSwitch);
    ret = InstanceManager::GetInstance()
                      .SampleMap_.at(NativeXComponentSample::PluginManager::GetInstance()->idList[1])
                      ->SetSurfaceNative(isAutoSwitch);
    if (ret != AVCODEC_SAMPLE_ERR_OK) {
        AVCODEC_SAMPLE_LOGI("Native setSurface failed");
    }
    return nullptr;
}

void NativeInit(napi_env env, void *data) {
    AsyncCallbackInfo *asyncCallbackInfo = static_cast<AsyncCallbackInfo *>(data);
    Hdr2Sdr *samples[2];
    std::string windowId[2];
    SampleInfo *sampleInfos = new SampleInfo[2];
    for(int32_t i = 0; i<2; ++i){
        samples[i] = Hdr2Sdr::getInstance(i);
        windowId[i] = "Surface" + std::to_string(i + 1);
    }
    sampleInfos[0].inputFd = asyncCallbackInfo->inputFd;
    sampleInfos[0].inputFileOffset = asyncCallbackInfo->inputOffset;
    sampleInfos[0].inputFileSize = asyncCallbackInfo->inputSize;
    sampleInfos[1].inputFd = asyncCallbackInfo->inputFd1;
    sampleInfos[1].inputFileOffset = asyncCallbackInfo->inputOffset1;
    sampleInfos[1].inputFileSize = asyncCallbackInfo->inputSize1;
    sampleInfos[1].isHdr2Sdr = true;
    for (int32_t i = 0; i < 2; ++i) {
        sampleInfos[i].window = NativeXComponentSample::PluginManager::GetInstance()->multiWindowsMap.at(windowId[i]);
        InstanceManager::GetInstance().InfoMap_.emplace(NativeXComponentSample::PluginManager::GetInstance()->idList[i],sampleInfos[i]);
        InstanceManager::GetInstance().SampleMap_.emplace(NativeXComponentSample::PluginManager::GetInstance()->idList[i],samples[i]);
        if(samples[i]->Init(sampleInfos[i]) == AVCODEC_SAMPLE_ERR_OK) {
            SetInitCallBack(asyncCallbackInfo, sampleInfos[i].durationTime, sampleInfos[i].videoWidth, sampleInfos[i].videoHeight);
        }else{
            SetCallBackResult(asyncCallbackInfo, -1, "Native Init failed");
            int32_t ret = samples[i]->Stop();
            if(ret != AVCODEC_SAMPLE_ERR_OK) {
                SetCallBackResult(asyncCallbackInfo, -1, "Native Stop failed");
            }
        }
    }
}

napi_value Hdr2SdrNative::Init(napi_env env, napi_callback_info info) {
    int32_t inputFd = true;
    int64_t inputOffset = 0;
    int64_t inputSize = 0;
    int32_t inputFd1 = true;
    int64_t inputOffset1 = 0;
    int64_t inputSize1 = 0;

    size_t argc = 6;
    napi_value args[6] = {nullptr}; // 6: arg count
    napi_get_cb_info(env, info, &argc, args, nullptr, nullptr);
    napi_get_value_int32(env, args[0], &inputFd);
    napi_get_value_int64(env, args[1], &inputOffset);
    napi_get_value_int64(env, args[2], &inputSize); 
    napi_get_value_int32(env, args[3], &inputFd1);
    napi_get_value_int64(env, args[4], &inputOffset1);
    napi_get_value_int64(env, args[5], &inputSize1); 
    napi_value promise;
    napi_deferred deferred;
    napi_create_promise(env, &deferred, &promise);

    AsyncCallbackInfo *asyncCallbackInfo = new AsyncCallbackInfo();

    asyncCallbackInfo->env = env;
    asyncCallbackInfo->asyncWork = nullptr;
    asyncCallbackInfo->deferred = deferred;
    asyncCallbackInfo->inputFd = inputFd;
    asyncCallbackInfo->inputOffset = inputOffset;
    asyncCallbackInfo->inputSize = inputSize;
    asyncCallbackInfo->inputFd1 = inputFd1;
    asyncCallbackInfo->inputOffset1 = inputOffset1;
    asyncCallbackInfo->inputSize1 = inputSize1;

    napi_value resourceName;
    napi_create_string_latin1(env, "NativeInit", NAPI_AUTO_LENGTH, &resourceName);
    napi_create_async_work(
        env, nullptr, resourceName, [](napi_env env, void *data) { NativeInit(env, data); },
        [](napi_env env, napi_status status, void *data) { DealCallBack(env, data); }, (void *)asyncCallbackInfo,
        &asyncCallbackInfo->asyncWork);
    napi_queue_async_work(env, asyncCallbackInfo->asyncWork);
    return promise;
}

EXTERN_C_START
static napi_value Init(napi_env env, napi_value exports)
{
    napi_property_descriptor desc[] = {
        {"Init", nullptr, Hdr2SdrNative::Init, nullptr, nullptr, nullptr, napi_default, nullptr},
        {"Play", nullptr, Hdr2SdrNative::Play, nullptr, nullptr, nullptr, napi_default, nullptr},
        {"Pause", nullptr, Hdr2SdrNative::Pause, nullptr, nullptr, nullptr, napi_default, nullptr},
        {"Resume", nullptr, Hdr2SdrNative::Resume, nullptr, nullptr, nullptr, napi_default, nullptr},
        {"Seek", nullptr, Hdr2SdrNative::Seek, nullptr, nullptr, nullptr, napi_default, nullptr},
        {"Stop", nullptr, Hdr2SdrNative::Stop, nullptr, nullptr, nullptr, napi_default, nullptr},
        {"Loop", nullptr, Hdr2SdrNative::Loop, nullptr, nullptr, nullptr, napi_default, nullptr},
        {"SetSurface", nullptr, Hdr2SdrNative::SetSurface, nullptr, nullptr, nullptr, napi_default, nullptr},
    };
    Hdr2Sdr::SetMaxNum(2);
    NativeXComponentSample::PluginManager::GetInstance()->Export(env, exports);
    napi_define_properties(env, exports, sizeof(desc) / sizeof(desc[0]), desc);
    return exports;
}
EXTERN_C_END

static napi_module demoModule = {
    .nm_version = 1,
    .nm_flags = 0,
    .nm_filename = nullptr,
    .nm_register_func = Init,
    .nm_modname = "hdr2sdr",
    .nm_priv = ((void*)0),
    .reserved = { 0 },
};

extern "C" __attribute__((constructor)) void RegisterHDR2SDRModule(void)
{
    napi_module_register(&demoModule);
}
